package nx.simple3d.data;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * This vector base is meant to hold all vectors used in this
 * engine. It holds them in a double array and thus which needs
 * to be initialized in advance with a correct size.
 * For this, register every object which owns vectors to this object
 * and call the compile() method when all objects are registered.
 * @author simon
 *
 */
public class VectorBase{
  protected double[] x;
  protected double[] y;
  protected double[] z;
  
  private List<VectorHandler> vectorHandlers;
  private Semaphore semaphore = new Semaphore(2);
  private static VectorBase instance;
  
  private VectorBase() {}
  
  public static VectorBase getInstance(){
    if(instance == null){
      instance = new VectorBase();
    }
    return instance;
  }
  
  private List<VectorHandler> getVectorHandlers(){
    if(vectorHandlers == null){
      vectorHandlers = new ArrayList<VectorHandler>();
    }
    return vectorHandlers;
  }
  
  public Semaphore getSemaphore(){
    return semaphore;
  }
  
  /**
   * Call this method once all VectorHandlers are registered.
   * This will finish the initialization of this vectorbase
   * and fill it with actual data provided by the VectorHandlers.
   */
  public void compile(){
    int size = 0;
    
    for(VectorHandler vh : getVectorHandlers()){
      vh.setVectorOffset(size);
      size += vh.getRequiredVectorCacheSize();
    }
    
    x = new double[size];
    y = new double[size];
    z = new double[size];
    
    for(VectorHandler vh : getVectorHandlers())
      vh.initializeVectorData();
  }
  
  /**
   * Registers a vector handler in order to initialize
   * this vector base on the compile() call.
   * @param vh The vector handler to be registered
   */
  public void registerVectorHandler(VectorHandler vh){
    getVectorHandlers().add(vh);
  }
  
  /**
   * Sets a single vector in this vector base.
   * @param index The index of the vector to be set.
   * @param x The new x-component
   * @param y The new y-component
   * @param z The new z-component
   */
  public void setVector(int index, double x, double y, double z){
    this.x[index] = x;
    this.y[index] = y;
    this.z[index] = z;
  }
  
  /**
   * Copies a single vector
   * @param from The index to copy from
   * @param to The index to copy to
   */
  public void copyVector(int from, int to){
    x[to] = x[from];
    y[to] = y[from];
    z[to] = z[from];
  }
  
  /**
   * Calculates the cross product of the vectors
   * a and b.
   * @param result The index of the vector to write the result to
   * @param a The index of the first vector
   * @param b The index of the second vector
   */
  public void crossProduct(int result, int a, int b){
    x[result] = y[a] * z[b] - z[a] * y[b];
    y[result] = z[a] * x[b] - x[a] * z[b];
    z[result] = x[a] * y[b] - y[a] * x[b];
  }
  
  /**
   * Multiplies the vector A with the given factor.
   * @param result The index of the vector to write the result to
   * @param a The index of the vector
   * @param factor The factor to multiply with
   */
  public void multiply(int result, int a, double factor){
    x[result] = x[a] * factor;
    y[result] = y[a] * factor;
    z[result] = z[a] * factor;
  }
  
  /**
   * Calculates the sum of the vectors
   * a and b.
   * @param result The index of the vector to write the result to
   * @param a The index of the first vector
   * @param b The index of the second vector
   */
  public void add(int result, int a, int b){
    x[result] = x[a] + x[b];
    y[result] = y[a] + y[b];
    z[result] = z[a] + z[b];
  }
  
  /**
   * Multiplies the vector b with the factor, adds it to a and writes
   * the result to the result vector.
   * @param result The index of the result vector
   * @param a Vector index
   * @param b Vector index
   * @param factor to multiply b before adding
   */
  public void add(int result, int a, int b, double factor){
    x[result] = x[a] + x[b] * factor;
    y[result] = y[a] + y[b] * factor;
    z[result] = z[a] + z[b] * factor;
  }
  
  /**
   * Normalizes the vector a to a length of 1
   * @param result The index to store the normalized vector to
   * @param a The index of the vector to normalize
   */
  public void normalize(int result, int a){
    setMagnitude(result, a, 1);
  }
  
  /**
   * Sets the magnitude (length) of the vector to the given value.
   * @param result The index to store the vector after setting the magnitude
   * @param a The vector which shall be set
   * @param magnitude The magnitude (length) to set the vector to
   */
  public void setMagnitude(int result, int a, double magnitude){
    multiply(result, a, magnitude / getMagnitude(a));
  }
  
  /**
   * @param index of the vector
   * @return The magnitude (length) of the vector
   */
  public double getMagnitude(int index){
    return Math.sqrt(
        Math.pow(x[index], 2) + 
        Math.pow(y[index], 2) + 
        Math.pow(z[index], 2));
  }
  
  /**
   * Formats a string representation of the vector
   * @param index The index of the vector
   * @return The formatted string
   */
  public String printVector(int index){
    return x[index] + " / " + y[index] + " / " + z[index];
  }
  
  /**
   * Processes the Count vectors from Start to scale all vectors that x equals projectionDistance.
   * By doing this it checks if the x-coordinate is less than projectionDistance and sorts those
   * out by setting its z-coordinate to Double.NAN
   * @param start The start index
   * @param count The number of vectors
   * @param projectionDistance
   */
  public void scaleXToValue(int start, int count, double projectionDistance){
    double scale;
    for(int i = start; i < start + count; i++){
      if(x[i] > projectionDistance){
        scale = projectionDistance / x[i];
        y[i] *= scale;
        z[i] *= scale;
      }
      else
        z[i] = Double.NaN;
    }
  }
}
